package inglar.autotc.shared.db;

import inglar.autotc.server.daemon.Daemon;
import inglar.autotc.shared.Values;

import java.io.Serializable;
import java.util.Vector;

/**
 * This class represents data from the database table `actions`.
 * @author Lee
 */
public class Actions implements Serializable{
	
	private static final long serialVersionUID = 7898350880091027480L;
	
	private String id = null;
	private String prio = null;
	private String action = null;
	
	String head = "actions@{";
	String tail = "}@";
	String fieldSeparator = "#";
	
	/**
	 * Gives an empty {@link Actions} object.
	 */
	public Actions() {
		
	}
	
	/**
	 * Creates an {@link Actions} object from a string.
	 * 
	 * @param string the string
	 * 
	 * @throws IllegalArgumentException if the specified string is invalid
	 */
	public Actions(String string) throws IllegalArgumentException {
		this();
		
		String functag = "Actions(String)";
		int numfields = 4;
		
		if(string == null) 
			throw new NullPointerException(functag + ": Argument 1 is null");
		
		string = string.trim();
		if(!string.startsWith(head) || !string.endsWith(tail)) 
			throw new IllegalArgumentException(functag + ": Argument 1 is not " +
					"wrapped with '" + head + "' and '" + tail + "'");
		
		string = string.substring(head.length(), string.indexOf(tail));
		
		String[] values = string.split(fieldSeparator);
		
		if(values.length != numfields) 
			throw new IllegalArgumentException(functag + ": The number of " +
					"fields is not " + numfields);
		
		try {
			this.setId(values[0]);
			this.setPrio(values[1]);
			this.setDetect(values[2]);
			this.setAction(values[3]);
		} catch(Throwable cause) {
			throw new IllegalArgumentException(functag + ": " + 
					cause.getMessage());
		}
	}
	
	/**
	 * Provides a valid string representation of this object.
	 */
	public String toString() {
		return head + this.getId() + fieldSeparator + this.getPrio() + 
				fieldSeparator + this.getDetect() + fieldSeparator + 
				this.getAction() + tail;
	}
	
	public String getId() {
		return this.id;
	}
	
	public String getPrio() {
		return this.prio;
	}
	
	public String getAction() {
		return this.action;
	}
	
	public String getDetect() {
		String retval = "";
		int dsize = this.detectVector.size();
		for(int i = 0; i < dsize; i++) {
			String[] t = this.detectVector.get(i);
			retval += t[0] + " " + t[1] + "; ";
		}
		return retval.trim();
	}
	
	public static final String detectSeparator = ";";
	
	public static final String[] detectOptions = {
//		"local_ip,",
		"local_port",
//		"local_ip:port",
		"foreign_ip",
		"foreign_port",
		"foreign_ip:port"
	};
	
	/**
	 * Checks whether a string is a known detect option or not.
	 * 
	 * @param string the string
	 * 
	 * @return 
	 * <code>true</code> if the given string is a detect option, 
	 * <code>false</code> otherwise
	 */
	public static boolean isDetectOption(String string) {
		for(int i = 0; i < detectOptions.length; i++)
			if(detectOptions[i].equals(string)) return true;
		return false;
	}
	
	/**
	 * Sets this instance's detect options.
	 * @param detect
	 * @throws IllegalArgumentException
	 */
	public void setDetect(String detect) throws IllegalArgumentException {
		if(detect == null) return; 
		String[] detectList = detect.split(detectSeparator);
		for(int i = 0; i < detectList.length; i++) {
			String t = detectList[i].trim();
			if(t.isEmpty()) continue;
			else {
				String[] detectLine = t.split(Values.REGEX_ALL_WHITESPACE);
				if(detectLine.length != 2) continue;
				else {
					this.addDetect(detectLine[0], detectLine[1]);
				}
			}
		}
//		if(this.getDetectRows() == 0) throw new IllegalArgumentException("detect must have a valid value");
	}
	
	private Vector<String[]> detectVector = new Vector<String[]>();
	
	/**
	 * Retrieves the detect option of the (i+1)'th detect criterion .
	 * 
	 * @param i detect index
	 * 
	 * @return 
	 * a detect option string,
	 * <code>null</code> if the (i+1)'th detect criterion does not exist
	 */
	public String getDetectOption(int i) {
		String retval = null;
		
		// get row from detectList and return first element
		try {
			String[] v = detectVector.get(i);
			if(v.length == 2) {
				retval = v[0];
			}
		} catch (Throwable caught) {
			
		}
		
		return retval;
	}
	
	/**
	 * Retrieves the detect option value of the (i+1)'th detect criterion 
	 * 
	 * @param i detect index
	 * 
	 * @return 
	 * a detect option value string, 
	 * <code>null</code> if the (i+1)'th detect criterion does not exist
	 */
	public String getDetectValue(int i) {
		String retval = null;
		
		// get row from detectList and return second element
		try {
			String[] v = detectVector.get(i);
			if(v.length == 2) {
				retval = v[1];
			}
		} catch (Throwable caught) {
			
		}
		
		return retval;
	}
	
	public int getDetectRows() {
		return detectVector.size();
	}
	
	/**
	 * Adds a detect option and its value, if this pair does not exist.
	 * 
	 * @param detectOption
	 * @param value
	 */
	public void addDetect(String detectOption, String value) 
			throws IllegalArgumentException {
		if(detectOption == null) 
			throw new IllegalArgumentException("detect option cannot be null");
		if(value == null)
			throw new IllegalArgumentException("detect value cannot be null");
		detectOption = detectOption.trim();
		if(detectOption.isEmpty())
			throw new IllegalArgumentException("detect option cannot be empty");
		value = value.trim();
		if(value.isEmpty())
			throw new IllegalArgumentException("detect value cannot be empty");
		String chars = " \b\t\n\f\r";
		if(value.contains(chars))
			throw new IllegalArgumentException("detect value cannot have in-between whitespace");
		if(!isDetectOption(detectOption))
			throw new IllegalArgumentException("detect option is invalid");
		
		// setup the pair
		String[] pair = new String[2];
		pair[0] = detectOption;
		pair[1] = value;
		
		// check all detect contents
		for(int i = 0; i < this.detectVector.size(); i++) {
			Object obj = this.detectVector.get(i);
			if(obj instanceof String[]) {
				String[] v = (String[]) obj;
				if(v.length != 2) continue;
				if(v[0].equals(pair[0]) && v[1].equals(pair[1])) {
					return;
				}
			}
		}
		this.detectVector.add(pair);
	}
	
	/* reset the entire object*/
	public void clearAll() {
		clearId();
		clearPrio();
		clearDetect();
		clearAction();
	}
	
	/* resets each field */
	public void clearId() { this.id = null; }
	public void clearPrio() { this.prio = null; }
	public void clearDetect() {
		this.detectVector.clear();
	}
	public void clearAction() { this.action = null; }
	
	/**
	 * Sets this {@link Actions} instance's id.
	 * @param id the id
	 */
	public void setId(String id) throws IllegalArgumentException {
		try {
			this.id = (new Integer(id)).toString();
		} catch (NumberFormatException nfe) {
			throw new IllegalArgumentException("id must be an integer");
		}
	}
	
	/**Sets this {@link Actions} instance's priority value.
	 * Giving a negative value will have it ignored by {@link Daemon}'s 
	 * algorithms.
	 * 
	 * @param prio the prio
	 */
	public void setPrio(String prio) throws IllegalArgumentException {
		if(prio == null) 
			throw new IllegalArgumentException("prio cannot be null");
		prio = prio.trim();
		if(prio.isEmpty())
			throw new IllegalArgumentException("prio cannot be empty");
		try {
			Integer i = new Integer(prio);
			this.prio = i.intValue() + "";
		} catch(Throwable cause) {
			throw new IllegalArgumentException("prio must be an integer");
		}
	}
	
	/**
	 * <b>To be used by GUI components.</b>
	 * 
	 * Sets this {@link Actions} instance's action command.
	 * 
	 * This value will only be significant inside {@link Daemon}'s 
	 * procedures.
	 * 
	 * @param action
	 * @throws IllegalArgumentException
	 */
	public void setAction(String action) 
			throws IllegalArgumentException {
		if(action == null) 
			throw new IllegalArgumentException("action cannot be null");
		action = action.trim();
		if(action.isEmpty())
			throw new IllegalArgumentException("action cannot be empty");
		this.action = action;
	}
	
	/**
	 * Used for testing.
	 */
	public static void main(String[] args) {
		System.out.println(Actions.class.getName());
		
		try {
			Actions a = new Actions("actions@{-1#-1#foreign_port 80; local_port 3306;#set tc1}@");
			System.out.println(a);
			System.out.println("id: " + a.getId());
			System.out.println("prio: " + a.getPrio());
			System.out.println("action: " + a.getAction());
			int rows = 0;
			System.out.println("detect rows: " + (rows = a.getDetectRows()));
			for(int i = 0; i < rows; i++) {
				System.out.println(a.getDetectOption(i) + " = " + a.getDetectValue(i));
			}
			if(rows == 0) System.out.println("No detect.");
			System.out.println(a);
		} catch (Throwable caught) {
			caught.printStackTrace();
		}
	}

}
